/**
 * Vector4.java
 * 
 * @date: 2012-02-28
 * @author: Xiaoyu Guo (MUN# 200982155)
 * This file is part of the course project for COMP 6752, 
 * Application of Computer Graphics. All the file is written
 * by myself.
 */
package mun.cs6752.project.model;

/** Defines a 4-tuple vector.
 * Vector4 are used to represent a 3D vector (x,y,z,w) in homogeneous coordinate.
 * @author Xiaoyu Guo
 */
public class Vector4 extends Vector {
    private static final int SIZE = 4;
    private static final int INDEX_X = 0;
    private static final int INDEX_Y = 1;
    private static final int INDEX_Z = 2;
    private static final int INDEX_W = 3;
    
    /**
     * @param source
     */
    public Vector4(double[] source) {
        super(source);
        
        if(size != SIZE){
            throw new IllegalArgumentException("size must be 4");
        }
    }
    
    public Vector4(){
        super(SIZE);
    }
    
    public Vector4(double x, double y, double z, double w){
        super(SIZE);
        setX(x);
        setY(y);
        setZ(z);
        setW(w);
    }
    
    public Vector4(Vector value){
        super(SIZE);

        checkNull(value);
        if(value.size < SIZE){
            throw new IllegalArgumentException("size must be 4");
        }
        
        setX(value.get(INDEX_X));
        setY(value.get(INDEX_Y));
        setZ(value.get(INDEX_Z));
        setW(value.get(INDEX_W));
    }
    
    public double getX(){
        return get(INDEX_X);
    }
    public void setX(double x){
        set(INDEX_X, x);
    }
    
    public double getY(){
        return get(INDEX_Y);
    }
    
    public void setY(double y){
        set(INDEX_Y, y);
    }
    
    public double getZ(){
        return get(INDEX_Z);
    }
    
    public void setZ(double z){
        set(INDEX_Z, z);
    }
    
    public double getW(){
        return get(INDEX_W);
    }
    
    public void setW(double w){
        set(INDEX_W, w);
    }
    
    /** Formalize
     * @return (x/w, y/w, z/w, 1)
     */
    public Vector4 formalize(){
        if(getW() == 0){
            throw new IllegalArgumentException("w equals to 0");
        }
        return new Vector4(
            getX() / getW(),
            getY() / getW(),
            getZ() / getW(),
            1
        );
    }
    
    /** Converts to Cartesian coordinate
     * @return (x/w, y/w, z/w)
     */
    public Vector3 getCartesian(){
        if(getW() == 0){
            throw new IllegalArgumentException("w equals to 0");
        }
        return new Vector3(
            getX() / getW(),
            getY() / getW(),
            getZ() / getW()
        );
    }

    /** 3D Translation by offset
     * @param tx offset on x axis
     * @param ty offset on y axis
     * @param tz offset on z axis
     * @return
     */
    public Vector4 translate(double tx, double ty, double tz){
        return new Vector4(
            getX() + tx * getW(),
            getY() + ty * getW(),
            getZ() + tz * getW(),
            getW()
        );
    }
    
    /** Scaling
     * @param sx scaling ratio on x axis
     * @param sy scaling ratio on y axis
     * @param sz scaling ratio on z axis
     * @return
     */
    public Vector4 scale(double sx, double sy, double sz){
        return new Vector4(
            getX() * sx,
            getY() * sy,
            getZ() * sz,
            getW()
        );
    }
    
    /** Rotate about x axis
     * @param theta the angle of rotation
     * @return
     */
    public Vector4 rotateX(double theta){
        Matrix4 r = new Matrix4();
        r.set(0, 0, 1);
        r.set(3, 3, 1);
        r.set(1, 1, Math.cos(theta));
        r.set(1, 2, -Math.sin(theta));
        r.set(2, 1, Math.sin(theta));
        r.set(2, 2, Math.cos(theta));
        
        return r.multiply(this);
    }
    
    /** Rotate about y axis
     * @param theta the angle of rotation
     * @return
     */
    public Vector4 rotateY(double theta){
        Matrix4 r = new Matrix4();
        r.set(1, 1, 1);
        r.set(3, 3, 1);
        r.set(0, 0, Math.cos(theta));
        r.set(2, 0, Math.sin(theta));
        r.set(0, 2, -Math.sin(theta));
        r.set(2, 2, Math.cos(theta));
        
        return r.multiply(this);
    }
    
    /** Rotates about z axis
     * @param theta the angle of rotation
     * @return
     */
    public Vector4 rotateZ(double theta){
        Matrix4 r = new Matrix4();
        r.set(2, 2, 1);
        r.set(3, 3, 1);
        r.set(0, 0, Math.cos(theta));
        r.set(0, 1, -Math.sin(theta));
        r.set(1, 0, Math.sin(theta));
        r.set(1, 1, Math.cos(theta));
        
        return r.multiply(this);
    }
    
    /** Shear XY (x and y changes with ratio of z)
     * @param hx 
     * @param hy
     * @return
     */
    public Vector4 shearXY(double hx, double hy){
        return new Vector4(
            getX() + hx * getZ(),
            getY() + hy * getZ(),
            getZ(),
            getW()
        );
    }
    
    /** Shear XZ
     * @param hx
     * @param hz
     * @return
     */
    public Vector4 shearXZ(double hx, double hz){
        return new Vector4(
            getX() + hx * getY(),
            getY(),
            getZ() + hz * getY(),
            getW()
        );
    }
    
    /** Shear YZ
     * @param hy
     * @param hz
     * @return
     */
    public Vector4 shearYZ(double hy, double hz){
        return new Vector4(
            getX(),
            getY() + hy * getX(),
            getZ() + hz * getX(),
            getW()
        );
    }
    
    /** Rotate about arbitrary direction
     * @param rotStart the starting point of rotation
     * @param rotEnd the ending point of rotation
     * @param theta the angle of rotation
     * @return
     */
    public Vector4 rotate(Vector4 rotStart, Vector4 rotEnd, double theta){
        rotStart = rotStart.formalize();
        rotEnd   = rotEnd.formalize();
        
        double x = rotEnd.getX() - rotStart.getX();
        double y = rotEnd.getY() - rotStart.getY();
        double z = rotEnd.getZ() - rotStart.getZ();
        double c = Math.cos(theta);
        double s = Math.sin(theta);
        
        Matrix4 r = new Matrix4();
        r.set(0, 0, x * x * (1 - c) + c);
        r.set(1, 0, x * y * (1 - c) - z * s);
        r.set(2, 0, x * z * (1 - c) + y * s);
        
        r.set(0, 1, y * x * (1 - c) + z * s);
        r.set(1, 1, y * y * (1 - c) + c);
        r.set(2, 1, y * z * (1 - c) - x * s);
        
        r.set(0, 2, z * x * (1 - c) - y * s);
        r.set(1, 2, z * y * (1 - c) + x * s);
        r.set(2, 2, z * z * (1 - c) + c);
        
        r.set(3, 3, 1);
        
        return r.multiply(this);
    }
}
